﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Remoting;
using System.Diagnostics;



namespace HyperVGuestToolbox
{
    static class Program
    {

        public static GuestSettings AppSettings;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            try
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                AppSettings = new GuestSettings();
                AppSettings.Init();


                if (!(Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Count() > 1))
                    Application.Run(new Main());
                else
                    MessageBox.Show(string.Format("{0} is already running", Application.ProductName), Application.ProductName);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }

    class DVDFile
    {
        public string filename { get; set; }
        public string folder { get; set; }
        string _fullPath ;
        public string fullpath
        {
            get { return _fullPath; }
            set{_fullPath = value;
                filename = Path.GetFileName (value);
                folder = Path.GetDirectoryName (value);
            }}
        public int? History { get; set; }
    }
    class GuestSettings
    {

        public string[] SearchFolders {get {return Properties.Settings.Default.SearchFolders.Split('\n');}}
        public string Extensions { get { return Properties.Settings.Default.Extensions; } }
        public int HistoryCount { get { return Properties.Settings.Default.HistoryCount; } }
        public System.Collections.Specialized.StringCollection FileHistory { get { return Properties.Settings.Default.History; } }
        public bool SearchSubDirectories { get { return Properties.Settings.Default.SearchSubDirectories; } }
        public string CurrentImage { get { return _CurrentImage; } }
        public string VirtualHost { get { return Properties.Settings.Default.VirtualHost ; } }
        public string VirtualGuest { get { return Properties.Settings.Default.VirtualGuest; } }


        public string _CurrentImage;

        public Dictionary<string, DVDFile> DVDFiles
        {
            get
            {
                if (_DVDFiles == null || _DVDFiles.Count ==0)
                    LoadFiles(SearchFolders, new List<DVDFile>());
                return _DVDFiles;
            }
        }
        

        Dictionary<string, DVDFile> _DVDFiles;

        public void AddToHistory(string filename)
        {
            if (FileHistory.Contains(filename))
            {
                FileHistory.Remove(filename);
                DVDFiles[filename].History = null ;
            }
            FileHistory.Insert(0, filename);

            SetHistoryValues();
            
            Properties.Settings.Default.Save();
        }


        public void SaveSettings(int HistoryCount, string Extensions, string SearchFolders, bool SearchSubDirectories, string VirtualGuest, string VirtualHost)
        {
            if (_DVDFiles == null) _DVDFiles = new Dictionary<string, DVDFile>();
            Properties.Settings.Default.HistoryCount = HistoryCount;
            Properties.Settings.Default.Extensions = Extensions ;
            Properties.Settings.Default.SearchSubDirectories = SearchSubDirectories;
            Properties.Settings.Default.History = Properties.Settings.Default.History == null ? new StringCollection() : Properties.Settings.Default.History;
            Properties.Settings.Default.VirtualHost = VirtualHost;
            Properties.Settings.Default.VirtualGuest = VirtualGuest;

            if (Properties.Settings.Default.SearchFolders != SearchFolders)
            {
                var newFolders = SearchFolders.Split('\n');
                var oldFolders = Properties.Settings.Default.SearchFolders.Split('\n');
                var foldersAdded = newFolders.Except(oldFolders);
                var foldersRemoved = oldFolders.Except(newFolders).ToDictionary<string,string>(f=>f);

                IEnumerable<DVDFile > DVDFileQuery = new List<DVDFile>();
                if (foldersRemoved.Count > 0)
                {
                    DVDFileQuery = (from file in _DVDFiles.Values
                                    where !(from folder in foldersRemoved.Keys where folder == file.folder select folder).Any()
                                    select file);

                    for (int i = FileHistory.Count - 1; i >= 0; i--)
                    {
                        if ((from folder in foldersRemoved.Values where FileHistory[i].StartsWith(folder) select folder).Any())
                            FileHistory.RemoveAt(i);
                    }
                    
                }

                DVDFileQuery = LoadFiles(foldersAdded, DVDFileQuery);
                
                Properties.Settings.Default.SearchFolders = SearchFolders;
            }
            Properties.Settings.Default.Save();

        }

        public GuestSettings()
        {

    
        }
        public void Init()
        {
            //need to be careful we are checking the correct hive
            if (String.IsNullOrEmpty(VirtualHost))
            {
                var result = PSHelper.ExecuteCommands(new string[] { "(Get-ItemProperty \"HKLM:SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters\") | Select-Object PhysicalHostName, VirtualMachineName" });
                if (result!=null && result.Count > 0)
                {
                    Properties.Settings.Default.VirtualGuest = (string)result[0].Properties["VirtualMachineName"].Value;
                    Properties.Settings.Default.VirtualHost = (string)result[0].Properties["PhysicalHostName"].Value;

                }
                else
                {
                    Properties.Settings.Default.VirtualGuest = "";
                    Properties.Settings.Default.VirtualHost = "";
                }
                Properties.Settings.Default.Save();
            }

            CheckForCurrentImage();
        }
        private void CheckForCurrentImage()
        {

            List<string> scriptLines = new List<string>();

            AddCoreDVDCommands(scriptLines);

            scriptLines.Add("if($ISOImage) {$ISOImage.Connection[0]}");

            var results = PSHelper.ExecuteCommands(scriptLines);
            if (results != null && results.Count > 0)
            {
                if (!string.IsNullOrEmpty(results[0].ToString()))
                    _CurrentImage = results[0].ToString();
            }
        }

        private static void AddCoreDVDCommands(List<string> scriptLines)
        {
            
                scriptLines.Add(string.Format("$VMHost=\"{0}\"",String.IsNullOrEmpty (Program.AppSettings.VirtualHost)?"localhost":Program.AppSettings.VirtualHost ));
            scriptLines.Add("$VMnamespace=\"root\\virtualization\"");
            scriptLines.Add(string.Format("$VMGuest=\"{0}\"",String.IsNullOrEmpty (Program.AppSettings.VirtualGuest)?"localhost":Program.AppSettings.VirtualGuest));
            scriptLines.Add("$vm = gwmi -ComputerName $VMHost -namespace $VMNamespace Msvm_ComputerSystem | ? {$_.ElementName -eq \"$VMGuest\"}" );
            //string scriptLines.Add("(Get-ItemProperty \"HKLM:SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters\")\n Select-Object PhysicalHostName, VirtualMachineName");
            scriptLines.Add("$vssd = gwmi -ComputerName $VMHost -namespace $VMNamespace -query \"Associators of {$vm} where ResultClass=Msvm_VirtualSystemSettingData\" |  where{$_.SettingType -eq 3} ");
            //Get IDE Controller
            scriptLines.Add("$ide = gwmi -ComputerName $VMHost -namespace $VMNamespace -query \"Associators of {$vssd} where ResultClass=Msvm_ResourceAllocationSettingData\" |   where{$_.ResourceType -eq 5 } "); //-and $_.Address -eq $ideNum
            scriptLines.Add("$dvdDrive = gwmi -ComputerName $VMHost -namespace $VMNamespace -query \"Associators of {$vssd} where ResultClass=Msvm_ResourceAllocationSettingData\" | where{$_.ResourceType -eq 16 }");//-and $_.Address -eq $lunNum -and $_.Parent -eq $ide.__PATH} 

            scriptLines.Add("if($dvdDrive) {");
            scriptLines.Add("$DrivePath=$dvdDrive.__Path.replace(\"\\\",\"\\\\\")}");
            scriptLines.Add("$ISOImage = gwmi -ComputerName $VMHost -namespace $VMNamespace -query \"Select * From MsVM_ResourceAllocationSettingData Where PARENT='$DrivePath'\"");
            
        }

        public void InsertImage(string image)
        {
            //http://blogs.technet.com/b/tonyso/archive/2009/08/18/hyper-v-how-to-change-iso-in-vm-virtual-dvd-drive-using-script.aspx

            var scriptLines = new List<string>();
            AddCoreDVDCommands(scriptLines);

            // Create a new DVD/CD virtual disk resource 
            scriptLines.Add("$vmms= gwmi -ComputerName $VMHost -namespace $VMnamespace Msvm_VirtualSystemManagementService ");
            scriptLines.Add(string.Format("$newFile = \"{0}\"", image));
            scriptLines.Add("if($newFile -eq \"\" -and $ISOImage) {");
            scriptLines.Add("   $result = $vmms.RemoveVirtualSystemResources($vm,@($ISOImage.GetText(1))) ");
            scriptLines.Add("}");

            scriptLines.Add("else {");
            scriptLines.Add("if ($ISOImage) {");
            scriptLines.Add("$ISOImage.Connection = $newFile");
            scriptLines.Add("$result = $vmms.ModifyVirtualSystemResources($vm,@($ISOImage.GetText(1))) }");
            scriptLines.Add("else {");
            scriptLines.Add("$allocationCapabilities = gwmi  -ComputerName $VMHost -namespace $VMnamespace Msvm_AllocationCapabilities -filter \"ResourceType = 21 AND ResourceSubtype = 'Microsoft Virtual CD/DVD Disk'\"");
            scriptLines.Add("$allocationPath = $allocationCapabilities.__PATH.replace(\"\\\",\"\\\\\") ");
            scriptLines.Add("$default = gwmi -ComputerName $VMHost -namespace $VMnamespace Msvm_SettingsDefineCapabilities -filter \"ValueRange = 0 AND GroupComponent = '$allocationPath'\"");
            scriptLines.Add("$dvd = [wmi]$default.PartComponent ");
            scriptLines.Add("#Set the properties on the virtual DVD to attach it to the right drive ");
            scriptLines.Add("$dvd.Address = 0 ");
            scriptLines.Add("$dvd.Parent = $dvdDrive.__PATH ");
            scriptLines.Add("$dvd.Connection = $newFile");

            scriptLines.Add("# Attach the new DVD to the virtual machine ");
            scriptLines.Add("$result = $vmms.AddVirtualSystemResources($vm,@($dvd.GetText(1))) }");
            scriptLines.Add("}");

            scriptLines.Add("if($result.ReturnValue -eq 4096){ ");
            scriptLines.Add("    # A Job was started, and can be tracked using its Msvm_Job instance ");
            scriptLines.Add("    $job = [wmi]$result.Job     # Wait for job to finish ");
            scriptLines.Add("    while($job.jobstate -lt 7){$job.get()} ");
            scriptLines.Add("   # Return the Job's error code ");
            scriptLines.Add("    return $job.ErrorCode }");
            scriptLines.Add("# Otherwise, the method completed ");
            scriptLines.Add("$result.ReturnValue");

            var result = PSHelper.ExecuteCommands(scriptLines);

            CheckForCurrentImage();

        }
        private IEnumerable<DVDFile> LoadFiles(IEnumerable<string> foldersAdded, IEnumerable<DVDFile> DVDFileQuery)
        {

            if (foldersAdded.Count() > 0)
            {
                SearchOption so = SearchSubDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;

                Regex FileMatch = new Regex(Extensions.Replace(".", "\\.").Replace(",", "|").Replace("*", ".*"), RegexOptions.IgnoreCase);
                foreach (var folder in foldersAdded)
                {
                    string _localFolder = folder;
                    if (Directory.Exists(folder))
                    {
                        DVDFileQuery = DVDFileQuery.Union(from f in Directory.GetFiles(_localFolder, "*.*", so)
                                                          where FileMatch.IsMatch(f)
                                                          select new DVDFile() { fullpath = f });
                    }
                }
            }

        
            _DVDFiles = DVDFileQuery.Select<DVDFile,DVDFile>(d=>d).ToDictionary<DVDFile, string>(f => f.fullpath);

            SetHistoryValues();

            return DVDFileQuery;
        }

        private void SetHistoryValues()
        {
            if (Program.AppSettings.FileHistory != null)
            {
                for (int i = 0; i < Program.AppSettings.FileHistory.Count; i++)
                {
                    string file = Program.AppSettings.FileHistory[i];
                    if (!_DVDFiles.ContainsKey(file))
                    {
                        DVDFiles.Add(file, new DVDFile() { fullpath = file });
                    }

                    _DVDFiles[file].History = i + 1;
             
                }
            }
        }

    }
}
